/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using DaveSexton.Controls;
using DaveSexton.DocProject.Sandcastle.Resources;

namespace DaveSexton.DocProject.Sandcastle.ContentManagement
{
	[DefaultProperty("TemplateHtml"), DefaultEvent("DesignerReady")]
	public sealed partial class XmlContentDocumentsEditor : UserControl
	{
		#region Public Properties
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public IEnumerable<ContentDocument> BaseDocuments
		{
			get
			{
				return new MarshalByRefEnumerable<ContentDocument>(GetBaseDocumentsInternal);
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public XmlContentDocument Document
		{
			get
			{
				if (documentListBox.Items.Count == 0)
					return null;
				else
					return (XmlContentDocument) documentListBox.Items[0];
			}
			set
			{
				if (value == null)
					Documents = null;
				else
					Documents = new XmlContentDocument[] { value };
			}
		}

		/// <summary>
		/// Gets or sets the documents that contain the individual content <see cref="Items"/> that may be edited.
		/// </summary>
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public IEnumerable<XmlContentDocument> Documents
		{
			get
			{
				return new MarshalByRefEnumerable<XmlContentDocument>(GetDocumentsInternal);
			}
			set
			{
				if (value != null && !IsDesignerReady)
					throw new InvalidOperationException(Resources.Errors.SetDocumentsWhileDesignerNotReady);

				ignoreSelectionChange = true;

				documentListBox.BeginUpdate();

				try
				{
					foreach (XmlContentDocument document in documentListBox.Items)
						document.ItemChanged -= document_ItemChanged;

					documentListBox.Items.Clear();

					float maxWidth = 0;

					if (value != null)
					{
						XmlDocumentationDisplayManager displayManager = DisplayManager;

						if (displayManager == null)
							throw new InvalidOperationException(Errors.XmlContentListEditorDisplayManagerNotSet);

						using (Graphics graphics = documentListBox.CreateGraphics())
						{
							foreach (XmlContentDocument item in value)
							{
								if (item != null)
								{
									item.ItemChanged += document_ItemChanged;

									documentListBox.Items.Add(item);

									SizeF size = graphics.MeasureString(item.DisplayName, documentListBox.Font);

									if (size.Width > maxWidth)
										maxWidth = size.Width;
								}
							}
						}

						// Set the width of the scroll bar but include some padding for bold fonts
						// since edited items become bold.
						documentListBox.HorizontalExtent = (int) (maxWidth + maxWidth * .1);
					}
					else
					{
						documentListBox.HorizontalExtent = 0;

						itemList.Items.Clear();
						lastItem = null;
						lastDocument = null;

						// NOTE: the itemList should be cleared before this property is assigned to null
						if (editor.IsDesignerReady)
							editor.SourceHtml = null;
					}

					ignoreSelectionChange = false;

					if (documentListBox.Items.Count > 0)
						ShowDefaultDocument();
					else
					{
						attributesSplitContainer.Panel1Collapsed = true;
						attributesSplitContainer.Enabled = false;
					}
				}
				finally
				{
					documentListBox.EndUpdate();
				}

				bool showDocumentListAndLabels = documentListBox.Items.Count > 1;

				contentDocumentsLabel.Visible = showDocumentListAndLabels;
				contentItemsLabel.Visible = showDocumentListAndLabels;
				listSplitContainer.Panel1Collapsed = !showDocumentListAndLabels;
			}
		}

		/// <summary>
		/// Gets the items for the selected document.
		/// </summary>
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public IEnumerable<ContentItem> Items
		{
			get
			{
				return new MarshalByRefEnumerable<ContentItem>(GetItemsInternal);
			}
		}

		[Browsable(false)]
		public ContentItem SelectedItem
		{
			get
			{
				return (itemList.SelectedIndex > -1) ? (ContentItem) itemList.SelectedItem : null;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public XmlContentDocument SelectedDocument
		{
			get
			{
				return (documentListBox.SelectedIndex > -1) ? (XmlContentDocument) documentListBox.SelectedItem : null;
			}
		}

		/// <remarks>This property has no effect when only one document is used since
		/// the document list will be hidden.</remarks>
		[Category("Appearance"), DefaultValue(101)]
		public int DocumentListHeight
		{
			get
			{
				return listSplitContainer.SplitterDistance;
			}
			set
			{
				listSplitContainer.SplitterDistance = value;
			}
		}

		[Category("Appearance")]
		public ContentEditorTab SelectedTab
		{
			get
			{
				return editor.SelectedTab;
			}
			set
			{
				editor.SelectedTab = value;
			}
		}

		[Category("Appearance")]
		public int AttributesGridHeight
		{
			get
			{
				return attributesSplitContainer.SplitterDistance;
			}
			set
			{
				attributesSplitContainer.SplitterDistance = value;
			}
		}

		[Category("Appearance")]
		public int ListWidth
		{
			get
			{
				return splitter.SplitterDistance;
			}
			set
			{
				splitter.SplitterDistance = value;
			}
		}

		/// <remarks>
		/// The <see cref="SourceHtmlChanged"/> event is not raised when this property is assigned at runtime.
		/// It's only raised when the user edits the document in design view or source view.
		/// </remarks>
		[Category("Data"), DefaultValue("")]
		public string SourceHtml
		{
			get
			{
				return editor.SourceHtml;
			}
			set
			{
				editor.SourceHtml = value;
			}
		}

		[Category("Data"), DefaultValue("")]
		public string SourceText
		{
			get
			{
				return editor.SourceText;
			}
			set
			{
				editor.SourceText = value;
			}
		}

		[Browsable(false)]
		public string DesignerDocumentHtml
		{
			get
			{
				return editor.DesignerDocumentHtml;
			}
		}

		[Category("Data"), DefaultValue("")]
		public string PreviewHtml
		{
			get
			{
				return editor.PreviewHtml;
			}
			set
			{
				editor.PreviewHtml = value;
			}
		}

		/// <summary>
		/// Gets or sets the designer's HTML template as raw HTML markup.
		/// </summary>
		/// <remarks>
		/// The template must contain valid HTML markup.
		/// <para>
		/// Optionally, a single <c>$BODY$</c> token that indicates the placement of the body HTML may be specified for when the <see cref="Body"/> 
		/// property is assigned.
		/// </para>
		/// </remarks>
		[Category("Appearance"), Description("Specifies the designer's HTML template as raw HTML markup.  " +
		 "Optionally, a single $BODY$ token that indicates the placement of the body HTML can be included for when the Body property is assigned."),
		RefreshProperties(RefreshProperties.All)]
		[Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
		public string TemplateHtml
		{
			get
			{
				return editor.TemplateHtml;
			}
			set
			{
				editor.TemplateHtml = value;
			}
		}

		[Category("Behavior"), DefaultValue(true)]
		public bool SourceXmlRequired
		{
			get
			{
				return editor.SourceXmlRequired;
			}
			set
			{
				editor.SourceXmlRequired = value;
			}
		}

		[Category("Data")]
		public string DefaultDocumentName
		{
			get
			{
				return defaultDocumentName;
			}
			set
			{
				defaultDocumentName = value;
			}
		}

		[Category("Data")]
		public string DefaultItemName
		{
			get
			{
				return defaultItemName;
			}
			set
			{
				defaultItemName = value;
			}
		}

		[Category("Behavior"), DefaultValue(true)]
		public bool EditListEnabled
		{
			get
			{
				// A field must be used instead of the list's Enabled property since the designer, for some reason, likes to
				// reset the value when saving, even if it's explicitly set in the property grid.  Using ShouldSerialize* instead
				// of DefaultValueAttribute does not help.  Using a backing field is the only thing that seems to help.
				return editListEnabled;
			}
			set
			{
				customItemsPanel.Visible = editListEnabled = value;

				if (value)
					itemList.ContextMenuStrip = itemListContextMenuStrip;
				else
					itemList.ContextMenuStrip = null;
			}
		}

		[Category("Behavior")]
		public Importer Importer
		{
			get
			{
				return editor.Importer;
			}
			set
			{
				editor.Importer = value;
			}
		}

		[Category("Behavior"), DefaultValue(true)]
		public bool DesignerEnabled
		{
			get
			{
				return editor.DesignerEnabled;
			}
			set
			{
				editor.DesignerEnabled = value;
			}
		}

		[Category("Behavior"), DefaultValue(true)]
		public bool PreviewEnabled
		{
			get
			{
				return editor.PreviewEnabled;
			}
			set
			{
				editor.PreviewEnabled = value;
			}
		}

		[Category("Behavior"), DefaultValue(false)]
		public bool AllowDesignerTabActiveWhenDisabled
		{
			get
			{
				return editor.AllowDesignerTabActiveWhenDisabled;
			}
			set
			{
				editor.AllowDesignerTabActiveWhenDisabled = value;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool IsSelectedItemValidXml
		{
			get
			{
				ContentItem item = SelectedItem;

				return (item != null && !item.IsValueDesignable) || editor.IsSourceHtmlValidXml;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool HasChanges
		{
			get
			{
				return editor.HasChanges;
			}
			set
			{
				editor.HasChanges = value;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public bool IsDesignerReady
		{
			get
			{
				return editor.IsDesignerReady;
			}
		}

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public XmlDocumentationDisplayManager DisplayManager
		{
			get
			{
				return editor.DisplayManager;
			}
			set
			{
				editor.DisplayManager = value;
			}
		}
		#endregion

		#region Private / Protected
		private bool editListEnabled, ignoreSelectionChange, suspendChangeNotification;
		private string defaultItemName, defaultDocumentName;
		private ContentDocument lastDocument;
		private ContentItem lastItem;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="XmlContentDocumentsEditor" /> class.
		/// </summary>
		public XmlContentDocumentsEditor()
		{
			InitializeComponent();

			attributesDataGridView.AutoGenerateColumns = false;
		}
		#endregion

		#region Methods
		public void AddTemplateStyleSheet(Uri file)
		{
			editor.AddTemplateStyleSheet(file);
		}

		public void PasteSourceText(string text)
		{
			editor.PasteSourceText(text);
		}

		private IEnumerable<XmlContentDocument> GetDocumentsInternal()
		{
			foreach (XmlContentDocument document in documentListBox.Items)
				yield return document;
		}

		private IEnumerable<ContentDocument> GetBaseDocumentsInternal()
		{
			foreach (ContentDocument document in documentListBox.Items)
				yield return document;
		}

		private IEnumerable<ContentItem> GetItemsInternal()
		{
			foreach (ContentItem item in itemList.Items)
				yield return item;
		}

		public int ImportAllResources()
		{
			return XmlContentManager.ImportAllResources(Importer, documentListBox.Items);
		}

		public void ShowDefaultDocument()
		{
			// by selecting the default document the SelectedIndexChanged event will be raised and the ShowDocument(ContentDocument) method will be called

			if (!string.IsNullOrEmpty(defaultDocumentName))
			{
				foreach (XmlContentDocument document in documentListBox.Items)
				{
					if (string.Equals(document.DisplayName, defaultDocumentName, StringComparison.OrdinalIgnoreCase))
					{
						ShowDocument(document);
						return;
					}
				}
			}

			if (documentListBox.Items.Count > 0)
				documentListBox.SelectedIndex = 0;
		}

		public void ShowDefaultItem()
		{
			if (documentListBox.SelectedIndex > -1)
			{
				// by selecting the default item the SelectedIndexChanged event will be raised and the ShowItem(ContentItem) method will be called

				if (!string.IsNullOrEmpty(defaultItemName))
				{
					foreach (ContentItem item in itemList.Items)
					{
						if (string.Equals(item.Name, defaultItemName, StringComparison.Ordinal))
						{
							ShowItem(item);
							return;
						}
					}
				}
			}

			if (itemList.Items.Count > 0)
				itemList.SelectedIndex = 0;
		}

		private void ShowDocument(XmlContentDocument document)
		{
			if (document == null)
				return;
			else if (documentListBox.SelectedItem != document)
			// raising the SelectedIndexChanged event will cause this method to be called again so the items list can be initialized
			{
				documentListBox.SelectedItem = document;
				documentListBox.TopIndex = documentListBox.Items.IndexOf(document);
			}
			else
			{
				editor.Namespaces = document.Namespaces;

				ignoreSelectionChange = true;

				itemList.BeginUpdate();

				try
				{
					itemList.Items.Clear();

					float maxWidth = 0;

					if (documentListBox.SelectedIndex > -1)
					{
						using (Graphics graphics = itemList.CreateGraphics())
						{
							foreach (ContentItem item in document.Items)
							{
								if (item != null && item.IsVisible)
								{
									itemList.Items.Add(item);

									SizeF size = graphics.MeasureString(item.Name, itemList.Font);

									if (size.Width > maxWidth)
										maxWidth = size.Width;
								}
							}
						}

						// Set the width of the scroll bar but include some padding for bold fonts
						// since edited items become bold.
						itemList.HorizontalExtent = (int) (maxWidth + maxWidth * .1);

						customItemComboBox.Items.Clear();

						foreach (string item in document.CustomItems)
							customItemComboBox.Items.Add(item);
					}
					else
						itemList.HorizontalExtent = 0;

					ignoreSelectionChange = false;

					if (itemList.Items.Count > 0)
						ShowDefaultItem();
					else
					{
						attributesSplitContainer.Panel1Collapsed = true;
						attributesSplitContainer.Enabled = false;
					}
				}
				finally
				{
					itemList.EndUpdate();
				}
			}
		}

		private void ShowItem(ContentItem item)
		{
			if (item == null || documentListBox.SelectedIndex == -1)
				return;
			else if (itemList.SelectedItem != item)
			// raising the SelectedIndexChanged event will cause this method to be called again so the editor can be initialized
			{
				itemList.SelectedItem = item;
				itemList.TopIndex = itemList.Items.IndexOf(item);
			}
			else
			{
				editor.HasChanges = item.IsDirty;

				try
				{
					attributesSplitContainer.SuspendLayout();

					if (item.ShowAttributes)
					{
						bool edit = item.CanEditAttributes;

						attributeColumn.ReadOnly = !edit;
						valueColumn.ReadOnly = !item.CanEditAttributeValues;

						attributesDataGridView.AllowUserToDeleteRows = edit;
						attributesDataGridView.AllowUserToAddRows = edit;
						attributesDataGridView.EditMode = (edit) ? DataGridViewEditMode.EditOnKeystrokeOrF2 : DataGridViewEditMode.EditOnEnter;

						attributesDataGridView.DataSource = item.Attributes;

						attributesSplitContainer.Panel1Collapsed = false;
						attributesSplitContainer.Visible = true;
					}
					else
						attributesSplitContainer.Panel1Collapsed = true;

					attributesSplitContainer.Enabled = true;

					if (item.Subsections != null && item.Subsections.Count > 0)
					{
						// TODO: subsections
					}
					else if (item.IsValueDesignable)
					// intialize the designer
					{
						editor.SourceXmlRequired = true;

						if (editor.IsDesignerReady)
							editor.SourceHtml = DisplayManager.ConvertHtmlToXml(item.Value, true, SelectedDocument.Namespaces);

						editor.DesignerEnabled = true;

						attributesSplitContainer.Panel2Collapsed = false;
						attributesSplitContainer.Visible = true;
					}
					else if ((item.EditModes & ContentItemEditModes.Source) == ContentItemEditModes.Source)
					// intialize source mode
					{
						editor.SourceXmlRequired = false;
						editor.SourceText = item.Value;
						editor.DesignerEnabled = false;

						attributesSplitContainer.Panel2Collapsed = false;
						attributesSplitContainer.Visible = true;
					}
					else
					{
						if (attributesSplitContainer.Panel1Collapsed)
							attributesSplitContainer.Visible = false;
						else
							attributesSplitContainer.Panel2Collapsed = true;
					}
				}
				finally
				{
					attributesSplitContainer.ResumeLayout();
				}
			}
		}

		private void DeleteItem(ContentItem item)
		{
			if (item.Delete())
			{
				itemList.Items.Remove(item);
				ShowDefaultItem();
			}
			else if (!string.IsNullOrEmpty(item.Value) && item.IsValueEditable)
			{
				item.Value = null;

				if (item.IsValueDesignable)
				{
					if (editor.IsDesignerReady)
						editor.SourceHtml = string.Empty;
				}
				else
					editor.SourceText = item.Value;
			}
			else
				System.Media.SystemSounds.Beep.Play();
		}

		public void CreateItem(string name)
		{
			XmlContentDocument document = SelectedDocument;

			if (document == null)
				throw new InvalidOperationException(Errors.ContentEditorNoSelectedDocument);

			ContentItem item = document.CreateItem(name);

			itemList.Items.Add(item);
			itemList.SelectedItem = item;
			itemList.TopIndex = itemList.Items.IndexOf(item);
		}
		#endregion

		#region Events
		private readonly object ItemChangedEvent = new object();
		private readonly object DesignerDragDropEvent = new object();
		private readonly object DesignerDragOverEvent = new object();
		private readonly object DesignerDragLeaveEvent = new object();
		private readonly object SourceDragDropEvent = new object();
		private readonly object SourceDragOverEvent = new object();
		private readonly object SourceDragLeaveEvent = new object();
		private readonly object SourceDragEnterEvent = new object();
		private readonly object DocumentListHeightChangedEvent = new object();
		private readonly object ListWidthChangedEvent = new object();
		private readonly object AttributesGridHeightChangedEvent = new object();

		/// <summary>
		/// Event raised when the <see cref="AttributesGridHeight"/> property value changes.
		/// </summary>
		[Category("Property Changed")]
		[Description("Event raised when the AttributesGridHeight property value changes.")]
		public event EventHandler AttributesGridHeightChanged
		{
			add
			{
				lock (AttributesGridHeightChangedEvent)
				{
					Events.AddHandler(AttributesGridHeightChangedEvent, value);
				}
			}
			remove
			{
				lock (AttributesGridHeightChangedEvent)
				{
					Events.RemoveHandler(AttributesGridHeightChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="AttributesGridHeightChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private void OnAttributesGridHeightChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (AttributesGridHeightChangedEvent)
			{
				handler = (EventHandler) Events[AttributesGridHeightChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}		

		/// <summary>
		/// Event raised when the <see cref="ListWidth"/> property value changes.
		/// </summary>
		[Category("Property Changed")]
		[Description("Event raised when the ListWidth property value changes.")]
		public event EventHandler ListWidthChanged
		{
			add
			{
				lock (ListWidthChangedEvent)
				{
					Events.AddHandler(ListWidthChangedEvent, value);
				}
			}
			remove
			{
				lock (ListWidthChangedEvent)
				{
					Events.RemoveHandler(ListWidthChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ListWidthChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private void OnListWidthChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (ListWidthChangedEvent)
			{
				handler = (EventHandler) Events[ListWidthChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="DocumentListHeight"/> property value changes.
		/// </summary>
		[Category("Property Changed")]
		[Description("Event raised when the DocumentListHeight property value changes.")]
		public event EventHandler DocumentListHeightChanged
		{
			add
			{
				lock (DocumentListHeightChangedEvent)
				{
					Events.AddHandler(DocumentListHeightChangedEvent, value);
				}
			}
			remove
			{
				lock (DocumentListHeightChangedEvent)
				{
					Events.RemoveHandler(DocumentListHeightChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DocumentListHeightChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private void OnDocumentListHeightChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (DocumentListHeightChangedEvent)
			{
				handler = (EventHandler) Events[DocumentListHeightChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content into the <see cref="source" /> <see cref="TextBox"/>.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content into the source text box.")]
		public event DragEventHandler SourceDragEnter
		{
			add
			{
				lock (SourceDragEnterEvent)
				{
					Events.AddHandler(SourceDragEnterEvent, value);
				}
			}
			remove
			{
				lock (SourceDragEnterEvent)
				{
					Events.RemoveHandler(SourceDragEnterEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceDragEnter" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnSourceDragEnter(DragEventArgs e)
		{
			DragEventHandler handler = null;

			lock (SourceDragEnterEvent)
			{
				handler = (DragEventHandler) Events[SourceDragEnterEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content away from the <see cref="source" /> <see cref="TextBox" />.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content away from the source text box.")]
		public event EventHandler SourceDragLeave
		{
			add
			{
				lock (SourceDragLeaveEvent)
				{
					Events.AddHandler(SourceDragLeaveEvent, value);
				}
			}
			remove
			{
				lock (SourceDragLeaveEvent)
				{
					Events.RemoveHandler(SourceDragLeaveEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceDragLeave" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		private void OnSourceDragLeave(EventArgs e)
		{
			EventHandler handler = null;

			lock (SourceDragLeaveEvent)
			{
				handler = (EventHandler) Events[SourceDragLeaveEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content over the <see cref="source" /> <see cref="TextBox" />.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content over the source text box.")]
		public event DragEventHandler SourceDragOver
		{
			add
			{
				lock (SourceDragOverEvent)
				{
					Events.AddHandler(SourceDragOverEvent, value);
				}
			}
			remove
			{
				lock (SourceDragOverEvent)
				{
					Events.RemoveHandler(SourceDragOverEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceDragOver" /> event.
		/// </summary>
		/// <param name="e"><see cref="DragEventArgs" /> object that provides the arguments for the event.</param>
		private void OnSourceDragOver(DragEventArgs e)
		{
			DragEventHandler handler = null;

			lock (SourceDragOverEvent)
			{
				handler = (DragEventHandler) Events[SourceDragOverEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when an object is dropped on the source text box.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when an object is dropped on the source text box.")]
		public event DragEventHandler SourceDragDrop
		{
			add
			{
				lock (SourceDragDropEvent)
				{
					Events.AddHandler(SourceDragDropEvent, value);
				}
			}
			remove
			{
				lock (SourceDragDropEvent)
				{
					Events.RemoveHandler(SourceDragDropEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="SourceDragDrop" /> event.
		/// </summary>
		/// <param name="e"><see cref="DragEventArgs" /> object that provides the arguments for the event.</param>
		private void OnSourceDragDrop(DragEventArgs e)
		{
			DragEventHandler handler = null;

			lock (SourceDragDropEvent)
			{
				handler = (DragEventHandler) Events[SourceDragDropEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content away from the designer.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content away from the designer.")]
		public event EventHandler<HtmlEditorEventArgs> DesignerDragLeave
		{
			add
			{
				lock (DesignerDragLeaveEvent)
				{
					Events.AddHandler(DesignerDragLeaveEvent, value);
				}
			}
			remove
			{
				lock (DesignerDragLeaveEvent)
				{
					Events.RemoveHandler(DesignerDragLeaveEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DesignerDragLeave" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnDesignerDragLeave(HtmlEditorEventArgs e)
		{
			EventHandler<HtmlEditorEventArgs> handler = null;

			lock (DesignerDragLeaveEvent)
			{
				handler = (EventHandler<HtmlEditorEventArgs>) Events[DesignerDragLeaveEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the mouse drags content over the designer.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when the mouse drags content over the designer.")]
		public event EventHandler<HtmlEditorEventArgs> DesignerDragOver
		{
			add
			{
				lock (DesignerDragOverEvent)
				{
					Events.AddHandler(DesignerDragOverEvent, value);
				}
			}
			remove
			{
				lock (DesignerDragOverEvent)
				{
					Events.RemoveHandler(DesignerDragOverEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DesignerDragOver" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnDesignerDragOver(HtmlEditorEventArgs e)
		{
			EventHandler<HtmlEditorEventArgs> handler = null;

			lock (DesignerDragOverEvent)
			{
				handler = (EventHandler<HtmlEditorEventArgs>) Events[DesignerDragOverEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when an object is dropped on the designer.
		/// </summary>
		[Category("Drag Drop")]
		[Description("Event raised when an object is dropped on the designer.")]
		public event EventHandler<HtmlEditorDragDropEventArgs> DesignerDragDrop
		{
			add
			{
				lock (DesignerDragDropEvent)
				{
					Events.AddHandler(DesignerDragDropEvent, value);
				}
			}
			remove
			{
				lock (DesignerDragDropEvent)
				{
					Events.RemoveHandler(DesignerDragDropEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DesignerDragDrop" /> event.
		/// </summary>
		/// <param name="e"><see cref="HtmlEditorEventArgs" /> object that provides the arguments for the event.</param>
		private void OnDesignerDragDrop(HtmlEditorDragDropEventArgs e)
		{
			EventHandler<HtmlEditorDragDropEventArgs> handler = null;

			lock (DesignerDragDropEvent)
			{
				handler = (EventHandler<HtmlEditorDragDropEventArgs>) Events[DesignerDragDropEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when an item's value changes.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when an item's value changes.")]
		public event EventHandler<ContentItemChangedEventArgs> ItemChanged
		{
			add
			{
				lock (ItemChangedEvent)
				{
					Events.AddHandler(ItemChangedEvent, value);
				}
			}
			remove
			{
				lock (ItemChangedEvent)
				{
					Events.RemoveHandler(ItemChangedEvent, value);
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="ItemChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="ContentItemChangedEventArgs" /> object that provides the arguments for the event.</param>
		private void OnItemChanged(ContentItemChangedEventArgs e)
		{
			EventHandler<ContentItemChangedEventArgs> handler = null;

			lock (ItemChangedEvent)
			{
				handler = (EventHandler<ContentItemChangedEventArgs>) Events[ItemChangedEvent];
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the designer is ready.
		/// </summary>
		[Category("State Changed")]
		[Description("Event raised when the designer is ready.")]
		public event EventHandler DesignerReady
		{
			add
			{
				editor.DesignerReady += value;
			}
			remove
			{
				editor.DesignerReady -= value;
			}
		}

		/// <summary>
		/// Event raised when the Preview tab becomes active.
		/// </summary>
		[Category("Action")]
		[Description("Event raised when the Preview tab becomes active.")]
		public event EventHandler Preview
		{
			add
			{
				editor.Preview += value;
			}
			remove
			{
				editor.Preview -= value;
			}
		}
		#endregion

		#region Event Handlers
		private void editor_DesignerReady(object sender, EventArgs e)
		{
			ShowDefaultItem();
		}

		private void editor_DesignerDragLeave(object sender, DaveSexton.Controls.HtmlEditorEventArgs e)
		{
			OnDesignerDragLeave(e);
		}

		private void editor_DesignerDragOver(object sender, DaveSexton.Controls.HtmlEditorEventArgs e)
		{
			OnDesignerDragOver(e);
		}

		private void editor_DesignerDragDrop(object sender, DaveSexton.Controls.HtmlEditorDragDropEventArgs e)
		{
			OnDesignerDragDrop(e);
		}

		private void editor_SourceDragLeave(object sender, EventArgs e)
		{
			OnSourceDragLeave(e);
		}

		private void editor_SourceDragOver(object sender, DragEventArgs e)
		{
			OnSourceDragOver(e);
		}

		private void editor_SourceDragEnter(object sender, DragEventArgs e)
		{
			OnSourceDragEnter(e);
		}

		private void editor_SourceDragDrop(object sender, DragEventArgs e)
		{
			OnSourceDragDrop(e);
		}

		private void editor_SourceHtmlChanged(object sender, HtmlChangedEventArgs e)
		{
			ContentItem item = (ContentItem) itemList.SelectedItem;

			if (item == null)
				// occurred in testing when a document in the Topic Designer did not have any items
				return;

			if (e.Reason != HtmlChangedReason.DocumentLoaded)
			{
				if (item != null && item.IsValueEditable)
				{
					suspendChangeNotification = true;

					try
					{
						item.Value = (item.IsValueDesignable) ? editor.SourceHtmlAsXmlWithoutDocPrefix : editor.SourceText;
					}
					catch (System.Xml.XmlException ex)
					{
						editor.SetInvalidXmlError(ex.Message);
						return;
					}
					finally
					{
						suspendChangeNotification = false;
					}
				}
			}

			OnItemChanged(new ContentItemChangedEventArgs(item, e.Reason));
		}

		private void document_ItemChanged(object sender, ContentItemEventArgs e)
		{
			if (!suspendChangeNotification)
				OnItemChanged(new ContentItemChangedEventArgs(e.Item, HtmlChangedReason.UserEdit));
		}

		private void splitter_SplitterMoved(object sender, System.Windows.Forms.SplitterEventArgs e)
		{
			OnListWidthChanged(e);
		}

		private void listSplitContainer_SplitterMoved(object sender, System.Windows.Forms.SplitterEventArgs e)
		{
			OnDocumentListHeightChanged(e);
		}

		private void attributesSplitContainer_SplitterMoved(object sender, SplitterEventArgs e)
		{
			OnAttributesGridHeightChanged(e);
		}

		private void itemList_MouseDown(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Right)
				itemList.SelectedIndex = itemList.IndexFromPoint(e.Location);
		}

		private void itemList_SelectedIndexChanged(object sender, EventArgs e)
		{
			ContentItem item = (ContentItem) itemList.SelectedItem;

			if (lastItem != item)
			{
				if (!ignoreSelectionChange)
					ShowItem(item);

				lastItem = item;
			}
		}

		private void documentListBox_SelectedIndexChanged(object sender, EventArgs e)
		{
			if (!ignoreSelectionChange)
			{
				XmlContentDocument document = (XmlContentDocument) documentListBox.SelectedItem;

				if (lastDocument != document)
				{
					ShowDocument(document);

					lastDocument = document;
				}
			}
		}

		private void documentListBox_DrawItem(object sender, DrawItemEventArgs e)
		{
			if (e.Index == -1)
				// occurred in testing
				return;

			e.DrawBackground();

			ContentDocument document = (ContentDocument) documentListBox.Items[e.Index];

			Font font = (document.IsDirty) ? new Font(e.Font ?? documentListBox.Font, FontStyle.Bold) : e.Font ?? documentListBox.Font;

			using (Brush brush = new SolidBrush(e.ForeColor))
			{
				e.Graphics.DrawString(document.DisplayName, font, brush, e.Bounds);
			}

			if ((e.State & DrawItemState.Selected) == DrawItemState.Selected
				&& (e.State & DrawItemState.NoFocusRect) == DrawItemState.None)
				e.DrawFocusRectangle();
		}

		private void itemList_DrawItem(object sender, DrawItemEventArgs e)
		{
			if (e.Index == -1)
				// occurred in testing
				return;

			e.DrawBackground();

			ContentItem item = (ContentItem) itemList.Items[e.Index];

			Font font = (item.IsDirty) ? new Font(e.Font ?? itemList.Font, FontStyle.Bold) : e.Font ?? itemList.Font;

			using (Brush brush = new SolidBrush(e.ForeColor))
			{
				e.Graphics.DrawString(item.ToString(), font, brush, e.Bounds);
			}

			if ((e.State & DrawItemState.Selected) == DrawItemState.Selected
				&& (e.State & DrawItemState.NoFocusRect) == DrawItemState.None)
				e.DrawFocusRectangle();
		}

		private void contentItemsLabel_Paint(object sender, PaintEventArgs e)
		{
			ControlPaint.DrawBorder3D(e.Graphics, contentItemsLabel.Bounds, Border3DStyle.RaisedOuter, Border3DSide.Bottom);
		}

		private void contentDocumentsLabel_Paint(object sender, PaintEventArgs e)
		{
			ControlPaint.DrawBorder3D(e.Graphics, contentItemsLabel.Bounds, Border3DStyle.RaisedOuter, Border3DSide.Bottom);
		}

		private void customItemComboBox_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Enter)
			{
				string name = customItemComboBox.Text;

				if (!string.IsNullOrEmpty(name))
				{
					customItemComboBox.Text = null;

					CreateItem(name);

					e.Handled = true;
				}
			}
		}

		private void customItemComboBox_SelectionChangeCommitted(object sender, EventArgs e)
		{
			string name = customItemComboBox.SelectedItem as string;

			if (name == null)
				name = customItemComboBox.Text;

			if (!string.IsNullOrEmpty(name))
			{
				customItemComboBox.Text = null;

				CreateItem(name);
			}
		}

		private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
		{
			DeleteItem((ContentItem) itemList.SelectedItem);
		}

		private void attributesDataGridView_KeyUp(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Escape && attributesDataGridView.CurrentRow.IsNewRow)
				attributesDataGridView.CancelEdit();
		}
		#endregion
	}

	/// <summary>
	/// Provides arguments for the <see cref="XmlContentDocumentsEditor.ItemChanged"/> event.
	/// </summary>
	[Serializable]
	public sealed class ContentItemChangedEventArgs : EventArgs
	{
		#region Public Properties
		public ContentItem Item
		{
			get
			{
				return item;
			}
		}

		public HtmlChangedReason Reason
		{
			get
			{
				return reason;
			}
		}
		#endregion

		#region Private / Protected
		private readonly ContentItem item;
		private readonly HtmlChangedReason reason;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ContentItemChangedEventArgs" /> class.
		/// </summary>
		public ContentItemChangedEventArgs(ContentItem item, HtmlChangedReason reason)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			if (!Enum.IsDefined(typeof(HtmlChangedReason), reason))
				throw new InvalidEnumArgumentException("reason", (int) reason, typeof(HtmlChangedReason));

			this.item = item;
			this.reason = reason;
		}
		#endregion
	}
}
